#include <lua.hpp>
#include "entity_factory.hpp"
#include "context.h"
#include "entity.h"
#include "message_logic.h"

extern engine::mpi MPI;

namespace engine
{

    void register_functions(lua_State *L)
    {
        lua_getglobal(L, "shared_entity_factory");
        //Resolve the overloaded method to the correct address by giving the compiler hints
        int (*Cfunc)(lua_State*) = shared_entity_factory::Create;
        lua_pushcfunction(L, Cfunc);
        lua_setfield(L, -2, "Create");

        lua_pop(L, 1);
    }

    //Server version with yet indeterminate id
    int shared_entity_factory::Create(lua_State *L, const char* classname,
                                      const fixedpoint::vec2d& p, const fixedpoint::vec2d& d)
    {
        unsigned int id;

        //Determine if classname is a valid name for an entity
        lua_getglobal(L, "prototype"); //1
        lua_getfield(L, -1, classname); //2
        if (lua_isnil(L, -1))
        {
            lua_pop(L, 2);
            return -1; //This class doesn't exist in the prototype table
        }
        //Determine the entity subtype
        lua_getfield(L, -1, "type");
        int subtype = luaL_checkint(L, -1);
        lua_pop(L, 1);

        //Get a permanent pointer to the name
        lua_getfield(L, -1, "name");
        const char* name = luaL_checkstring(L, -1);
        lua_pop(L, 1);

        //Add the C-side entity to the correct collection
        //making the collections complete
        game::Entity* ent;
        switch (subtype)
        {
        case game::PHYSICS :
            //Add a physics object
            ent = &context::Instance().world.physics_entities.add(game::Physics_Entity(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.physics_entities[subtype_idx].id = id;
            break;
        case game::VEHICLE :
            //Add a vehicle object
            ent = &context::Instance().world.vehicle_entities.add(game::Vehicle(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.vehicle_entities[subtype_idx].id = id;
            break;
        case game::ENTITY :
        default:
            //Add a generic entity object
            ent = &context::Instance().world.entities.add(game::Entity(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.entities[subtype_idx].id = id;
            break;
        }
        context::Instance().world.shared_entities.add(ent, ent->id);
        #ifdef SERVING
        Entity_Create_Message msg(*ent);
        MPI.enqueue_all_remote_hosts(&msg);
        #endif
        id = ent->id;

        //Okay, we found the valid prototype, now copy it to the shared entity
        //table. Stack looks like this:
        /*
          -class prototype {} value <-- top
          -prototype table
        */
        //First, make a copy of the table on the top of the stack
        //Do this by calling the Derive() function
        lua_getfield(L, -1, "Derive"); //3
        lua_pushvalue(L, -2); //Copy the base class prototype reference //4
        lua_newtable(L); //Create a new table. This is the instance //5
        lua_pushinteger(L, (lua_Integer) id); //6
        lua_setfield(L, -2, "id"); //5
        lua_pcall(L, 2, 1, 0); //Call the derive function //3
        /* Stack:
           -New object
           -prototype[classname]
           -prototype table
        */
        //So at this point, the new table is ready to be run. We gotta
        //insert it into the correct table now
        lua_getglobal(L, "shared_entities"); //4
        lua_pushinteger(L, (lua_Integer) id); //5
        lua_replace(L, -4); //overwrite proto class with index //4
        lua_replace(L, -4); //overwrite prototype table with shared_entity table //3
        lua_settable(L, -3);//stick the new table into shared_entities[id] //1

        //Add an entity to the C side
        //shared_entity_array[id] = new game::entity;

        /*Stack:
          -shared_entities {}
        */
        //Now run the OnCreate() callback
        int creator_id = 0; //Wat.
        lua_pushinteger(L, (lua_Integer) id); //2
        lua_gettable(L, -2); //replaces key (id index) with value //2
        lua_getfield(L, -1, "OnCreate"); //push function //3
        lua_pushvalue(L, -2); //'self' as argument 1 //4
        lua_pushinteger(L, (lua_Integer)creator_id); //5
        lua_gettable(L, -5); //replaces key with creator reference/5
        int res = lua_pcall(L, 2, 0, 0); //run callback //2
        if (res)
        {
            fprintf(stderr, "entity_factory::Create(): %s\n", lua_tostring(L, -1));
            lua_pop(L, 3);
            return -1;
        }
        /*Stack:
          -shared_entities[id]
          -shared_entities {}
        */
        lua_pop(L, 2);

        return (int) id;
    }

    int shared_entity_factory::Create(lua_State *L, const char* classname,
                                      const fixedpoint::vec2d& p, const fixedpoint::vec2d& d,
                                      unsigned int id)
    {
        //unsigned int id;

        //Determine if classname is a valid name for an entity
        lua_getglobal(L, "prototype"); //1
        lua_getfield(L, -1, classname); //2
        if (lua_isnil(L, -1))
        {
            lua_pop(L, 2);
            return -1; //This class doesn't exist in the prototype table
        }
        //Determine the entity subtype
        lua_getfield(L, -1, "type");
        int subtype = luaL_checkint(L, -1);
        lua_pop(L, 1);

        //Get a permanent pointer to the name
        lua_getfield(L, -1, "name");
        const char* name = luaL_checkstring(L, -1);
        lua_pop(L, 1);

        //Add the C-side entity to the correct collection
        //making the collections complete
        game::Entity* ent;
        switch (subtype)
        {
        case game::PHYSICS :
            //Add a physics object
            ent = &context::Instance().world.physics_entities.add(game::Physics_Entity(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.physics_entities[subtype_idx].id = id;
            break;
        case game::VEHICLE :
            //Add a vehicle object
            ent = &context::Instance().world.vehicle_entities.add(game::Vehicle(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.vehicle_entities[subtype_idx].id = id;
            break;
        case game::ENTITY :
        default:
            //Add a generic entity object
            ent = &context::Instance().world.entities.add(game::Entity(p,d,name));
            //ent.id = context::Instance().world.shared_entities.add(&ent);
            //context::Instance().world.entities[subtype_idx].id = id;
            break;
        }
        context::Instance().world.shared_entities.add_at(ent, ent->id);

        //Okay, we found the valid prototype, now copy it to the shared entity
        //table. Stack looks like this:
        /*
          -class prototype {} value <-- top
          -prototype table
        */
        //First, make a copy of the table on the top of the stack
        //Do this by calling the Derive() function
        lua_getfield(L, -1, "Derive"); //3
        lua_pushvalue(L, -2); //Copy the base class prototype reference //4
        lua_newtable(L); //Create a new table. This is the instance //5
        lua_pushinteger(L, (lua_Integer) id); //6
        lua_setfield(L, -2, "id"); //5
        lua_pcall(L, 2, 1, 0); //Call the derive function //3
        /* Stack:
           -New object
           -prototype[classname]
           -prototype table
        */
        //So at this point, the new table is ready to be run. We gotta
        //insert it into the correct table now
        lua_getglobal(L, "shared_entities"); //4
        lua_pushinteger(L, (lua_Integer) id); //5
        lua_replace(L, -4); //overwrite proto class with index //4
        lua_replace(L, -4); //overwrite prototype table with shared_entity table //3
        lua_settable(L, -3);//stick the new table into shared_entities[id] //1

        //Add an entity to the C side
        //shared_entity_array[id] = new game::entity;

        /*Stack:
          -shared_entities {}
        */
        //Now run the OnCreate() callback
        int creator_id = 0; //Wat.
        lua_pushinteger(L, (lua_Integer) id); //2
        lua_gettable(L, -2); //replaces key (id index) with value //2
        lua_getfield(L, -1, "OnCreate"); //push function //3
        lua_pushvalue(L, -2); //'self' as argument 1 //4
        lua_pushinteger(L, (lua_Integer)creator_id); //5
        lua_gettable(L, -5); //replaces key with creator reference/5
        int res = lua_pcall(L, 2, 0, 0); //run callback //2
        if (res)
        {
            fprintf(stderr, "entity_factory::Create(): %s\n", lua_tostring(L, -1));
            lua_pop(L, 3);
            return -1;
        }
        /*Stack:
          -shared_entities[id]
          -shared_entities {}
        */
        lua_pop(L, 2);

        return (int) id;
    }

    int shared_entity_factory::Create(lua_State *L)
    {
        //Lua function called when it wants to create an entity
        //ONLY call this from lua, and on the server side
        fixedpoint::vec2d pos;
        fixedpoint::vec2d dir; //Get these positions from lua. For now they are just 0
        const char* classname = luaL_checkstring(L, 1);
        int id = Create(L, classname, pos, dir);
        if (id < 0)
        {
            //Error condition, return nil
            lua_pushnil(L);
            return 1;
        }
        //Push a reference to the new object to the stack
        lua_getglobal(L, "shared_entities");
        lua_pushinteger(L, (lua_Integer) id);
        lua_gettable(L, -2);
        lua_remove(L, -2);
        //Now all that's left is the table we just created
        return 1;
    }


    int shared_entity_factory::Destroy(lua_State *L, unsigned int id)
    {
        /*
        if (context::Instance().world.shared_entities.at(id) == NULL)
            return -1; //Already deleted?
        //Run the destroy callback in lua
        lua_getglobal(L, "shared_entities");
        lua_pushinteger(L, (lua_Integer) id);
        lua_gettable(L, -2);
        lua_getfield(L, -1, "OnDestroy");
        int callback_res = lua_pcall(L, 0, 0, 0);
        if (callback_res)
        {
            fprintf(stderr, "entity_factory::Destroy(): %s\n", lua_tostring(L, -1));
            lua_pop(L, 3);
            return -1; //Error in calling callback
        }
        */
        /*Stack:
          -shared_entities[id]
          -shared_entities {}
        */
        /*
        //Now free the lua object on the lua side
        lua_pushinteger(L, (lua_Integer) id);
        lua_replace(L, -2);
        lua_pushnil(L);
        lua_settable(L, -3); //Set shared_entities[id] to nil allowing it to be gc'd
        lua_pop(L, 1);
        delete shared_entity_array[id];
        shared_entity_array[id] = NULL;
        free_list.push(id);
        */
        return 0;
    }

    int shared_entity_factory::Destroy(lua_State *L)
    {
        //The table to be destroyed should be right on the top of the stack
        lua_getfield(L, -1, "id");
        int dead_id = lua_tointeger(L, -1);
        Destroy(L, dead_id);
        lua_pop(L, 1);
        return 0;
    }


}
